package ruru.server;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
import java.util.StringTokenizer;

import ruru.client.backend.RuruUtility;
import ruru.client.backend.rpcWrapper.BankItemWrapper;
import ruru.client.backend.rpcWrapper.BankWrapper;
import ruru.client.backend.rpcWrapper.DkpChangeWrapper;
import ruru.client.backend.rpcWrapper.LiteRaiderWrapper;
import ruru.client.backend.rpcWrapper.RaidLootRowWrapper;
import ruru.client.backend.rpcWrapper.RaidWrapper;
import ruru.client.backend.rpcWrapper.RaiderWrapper;
import ruru.client.backend.rpcWrapper.StandingsDataRow;
import ruru.client.backend.rpcWrapper.StandingsDataWrapper;
import ruru.client.backend.rpcWrapper.TooltipInfoWrapper;
import ruru.client.services.DataService;
import ruru.server.backend.Boss;
import ruru.server.backend.BossDrop;
import ruru.server.backend.DkpChange;
import ruru.server.backend.Dungeon;
import ruru.server.backend.LootAssignment;
import ruru.server.backend.Raid;
import ruru.server.backend.RaidPointException;
import ruru.server.backend.Raider;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class DataServiceImpl extends RemoteServiceServlet implements DataService {
	private static final long serialVersionUID = 6205948847969316480L;
	private HashMap<String, TooltipInfoWrapper> tooltipInfoCache = new HashMap<String, TooltipInfoWrapper>();
	private HashMap<String, String> itemToIdCache = new HashMap<String, String>();

	/**
	 * On tomcat server "C:/Program Files/Apache Software Foundation/Tomcat 5.5/webapps/ROOT/data"; On kyruru.net server
	 * "/home/kyruru/public_html/ruruDkp/data"; On client "src/ruru/public/data";
	 * 
	 * TODO: fix this ghetto stuff by making a config file somehow
	 */
	private final String root = "src/ruru/public/data";

	/**
	 * "/home/kyruru/public_html/ruruDkp/standings.txt" "standings.txt"
	 */
	private final String standings = "standings.txt";

	private final String raidDir = root + "/raids";
	private final String dungeonFile = root + "/dungeons.txt";
	private final String bossFile = root + "/bosses.txt";
	private final String raiderFile = root + "/raiders.txt";
	private final String retiredFile = root + "/retired.txt";

	private final String reducksFile = root + "/banks/reducks.txt";

	// private final String newsFile = root + "/html/news.txt";
	private final String rulesFile = root + "/html/rules.txt";

	private final String itemList = root + "/itemlist.xml";

	private HashMap<String, Raider> raiders = new HashMap<String, Raider>();
	private HashMap<String, Boss> bosses = new HashMap<String, Boss>();
	private HashMap<String, Dungeon> dungeons = new HashMap<String, Dungeon>();
	private List<Raid> raids = new ArrayList<Raid>();

	long start;

	public DataServiceImpl() {
		SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, yyyy h:mm:ssa");
		start = System.currentTimeMillis();
		System.out.println(sdf.format(new Date()) + " starting server: " + (System.currentTimeMillis() - start) + "ms");

		readItemListXML();
		dungeons = getDungeonData();
		bosses = getBossData();
		raiders = getRaiderData();
		raids = getRaidData();
		for(Raid raid : raids) {
			raid.updateRaiderInfo();
			enforceDkpCap(raid.getStart());
		}
		System.out.println("updateRaiderInfo done: " + (System.currentTimeMillis() - start) + "ms");

		outputReduxDKP(true);
	}

	private void enforceDkpCap(Date raidStart) {
		for(Raider raider : raiders.values()) {
			/**
			 * DKP cap at 150, but raised to 200 between 08/27/2007 and 09/24/2007
			 */
			double dkpCap = 150.0;
			SimpleDateFormat sdfDkpCap = new SimpleDateFormat("hh:mma EEE MM-dd-yyyy");
			try {
				Date capLiftStart = sdfDkpCap.parse("5:00pm Mon 08-27-2007");
				Date capLiftEnd = sdfDkpCap.parse("5:00pm Thu 09-27-2007");

				if(raidStart.getTime() > capLiftStart.getTime() && raidStart.getTime() < capLiftEnd.getTime()) {
					dkpCap = 200.0;
				}
			} catch(Exception e) {
				e.printStackTrace();
			}

			if(raider.getPoints() > dkpCap) {
				raider.addDkpChange("Cap Restriction", dkpCap - raider.getPoints(), raidStart);
				// System.out.println(raider.getPoints() + " " + (dkpCap - raider.getPoints()));
				raider.modifyPoints(dkpCap - raider.getPoints());
			}
		}
	}

	private void outputReduxDKP(boolean output) {
		try {
			FileWriter writer = new FileWriter(standings);
			BufferedWriter out = new BufferedWriter(writer);
			if(output) {
				out.write("DkpLTable = {" + "\n");

				Set<String> raiderSet = raiders.keySet();
				Iterator<String> raiderIterator = raiderSet.iterator();
				while(raiderIterator.hasNext()) {
					String raiderNameKey = (String) raiderIterator.next();
					Raider raider = (Raider) raiders.get(raiderNameKey);
					out.write("[\"" + RuruUtility.capFirstLetter(raider.getName()) + "\"] = "
							+ twoDecimals(raider.getLiquidity()) + "," + "\n");
				}

				out.write("}" + "\n");
				out.write("ItemHistory = {}" + "\n");
				out.write("DkpTable = {" + "\n");

				raiderSet = raiders.keySet();
				raiderIterator = raiderSet.iterator();
				while(raiderIterator.hasNext()) {
					String raiderNameKey = (String) raiderIterator.next();
					Raider raider = (Raider) raiders.get(raiderNameKey);
					out.write("[\"" + RuruUtility.capFirstLetter(raider.getName()) + "\"] = "
							+ twoDecimals(raider.getPoints()) + "," + "\n");
				}

				out.write("}" + "\n");
			}

			out.flush();
		} catch(IOException e) {
			e.printStackTrace();
		}

	}

	public RaiderWrapper getRaiderWrapperByName(String name) {
		System.out.println(name);
		Raider raider = raiders.get(name);
		List<DkpChange> changes = raider.getDkpChanges();
		List<DkpChangeWrapper> changeWrapper = new ArrayList<DkpChangeWrapper>();
		for(DkpChange change : changes) {
			changeWrapper.add(new DkpChangeWrapper(change.getDescription(), change.getValue(), change.getDate()));
		}

		RaiderWrapper wrapper = new RaiderWrapper(name, raider.getRaidClass(), raider.getPoints(), raider.isRetired(),
				changeWrapper);

		return wrapper;
	}

	public List<LiteRaiderWrapper> getLiteRaiderWrappers() {
		List<LiteRaiderWrapper> wrappers = new ArrayList<LiteRaiderWrapper>();
		for(Raider raider : raiders.values()) {
			wrappers.add(new LiteRaiderWrapper(raider.getName(), raider.getRaidClass(), raider.isRetired()));
		}

		return wrappers;
	}

	public RaidWrapper getRaidByIndex(int index) {
		Raid raid = (Raid) raids.get(index);
		List<Raider> members = raid.getParticipants();
		StringBuffer participants = new StringBuffer();

		Collections.sort(members);
		for(Raider raider : members) {
			participants.append(raider.getColoredHtmlNameString() + " ");
		}

		List<RaidLootRowWrapper> raidLootRowWrappers = new ArrayList<RaidLootRowWrapper>();
		List<LootAssignment> assignments = raid.getLootAssignments();
		for(LootAssignment loot : assignments) {
			String recipient = RuruUtility.capFirstLetter(loot.getRaider().getName());
			raidLootRowWrappers.add(new RaidLootRowWrapper(recipient, loot.getBossDrop().getName(), loot.getBossDrop()
					.getBoss().getName(), loot.getBidAmount()));
		}

		String dungeonName = raid.getDungeon().getName();
		return new RaidWrapper(raid.getStart(), raid.getEnd(), raid.getPointsAwarded(), raid.getDesc(), dungeonName,
				participants.toString(), raidLootRowWrappers);
	}

	public List<String> getRaidTitles() {
		List<String> titles = new ArrayList<String>();
		SimpleDateFormat sdf = new SimpleDateFormat("EEE MM-dd-yyyy");
		for(Raid raid : raids) {
			titles.add(sdf.format(raid.getStart()) + " " + raid.getDungeon().toString());
		}

		return titles;
	}

	private String twoDecimals(double value) {
		NumberFormat nf = NumberFormat.getInstance();
		nf.setMaximumFractionDigits(2);
		return nf.format(value);
	}

	/**
	 * Complete. Not true xml parsing: just tokenizes itemlist.xml by double quote.
	 */
	private void readItemListXML() {
		int i = 0;

		try {
			Scanner scanner = new Scanner(new BufferedReader(new FileReader(itemList)));
			while(scanner.hasNext()) {
				String line = scanner.nextLine();
				if(RuruUtility.removeStartingSpaces(line).substring(0, 8).equals("<wowitem")) {
					String[] parts = line.split("[\"]");
					itemToIdCache.put(parts[1].toLowerCase(), parts[3]);
					i++;
				}
			}

			System.out.println("readItemListXML done: " + i + " items loaded. " + (System.currentTimeMillis() - start)
					+ "ms");
			scanner.close();
		} catch(Exception ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * Complete. Called from client.
	 */
	public String getRules() {
		// readItemListXML();
		return getHTML(rulesFile);
	}

	/**
	 * Complete. Reads any text file and stuffs the contents into a string.
	 * 
	 * @param filename
	 * @return
	 */
	private String getHTML(String filename) {
		StringBuffer buffer = new StringBuffer();

		try {
			Scanner scanner = new Scanner(new BufferedReader(new FileReader(filename)));
			while(scanner.hasNext()) {
				buffer.append(scanner.nextLine());
			}
			scanner.close();
		} catch(Exception ex) {
			ex.printStackTrace();
		}

		return buffer.toString();
	}

	/**
	 * Complete. Reads World of Warcraft item data from wowhead.com. Caches data received and requires the caller to
	 * watch for items like "insignia of the alliance" that are multiple unique items for the same name by sending the ID
	 * instead of the name.
	 */
	public TooltipInfoWrapper getTooltipInfo(String name, boolean ambiguous) {
		String root = "http://www.wowhead.com/?item=";
		if(tooltipInfoCache.containsKey(name)) {
			System.out.print(name + " was cached!");
			return tooltipInfoCache.get(name);
		} else {
			System.err.print(name + " was not cached... getting link from wowhead at ");
			StringBuffer buffer = new StringBuffer();

			try {
				URL link;
				if(ambiguous) {
					// if the item is ambiguous, the "name" is already an ID
					link = new URL(root + name);
				} else {
					// if the item isn't ambiguous, the "name" is just a name, and we
					// need to look up the item's ID in the cache
					link = new URL(root + itemToIdCache.get(name.toLowerCase()));
				}

				System.err.print(link.toString());
				InputStream in = link.openStream();
				BufferedInputStream stream = new BufferedInputStream(in);
				Scanner scanner = new Scanner(stream);

				while(scanner.hasNext()) {
					String line = scanner.nextLine();
					buffer.append(line);
				}

			} catch(MalformedURLException e) {
				e.printStackTrace();
			} catch(IOException e) {
				e.printStackTrace();
			}
			
			System.err.println("");
			String tmp = buffer.toString();
			String rawInfo; // for use to find the rarityClass
			int start;
			int end;

			/* info: tooltip text */
			String tooltipBeginning = "class=\"tooltip\" style=\"float: left; padding-top: 1px\">";
			start = tmp.indexOf(tooltipBeginning) + tooltipBeginning.length();
			end = tmp.indexOf("</table></div>", start);
			String info = tmp.substring(start, end + 14);

			rawInfo = info;
			info = "<div class='tooltip-text'>" + info;
			info = info.replaceFirst("<table", "<KashiraKashira!");
			info = info.replace("<table", "<table width='100%'");
			info = info.replaceFirst("<KashiraKashira!", "<table class='kashira'");
			System.out.println(info);

			/* iconPath */
			tmp = buffer.toString();
			start = tmp.indexOf("Icon.create('") + 13;
			end = tmp.indexOf("'", start + 1);
			String iconPath = tmp.substring(start, end);
			System.out.println("result in serverside GetItemIconFileName:" + iconPath);

			/* rarityClass */
			start = rawInfo.indexOf("class=\"");
			end = rawInfo.indexOf("\"", start + 7);
			String rarityClass = rawInfo.substring(start + 7, end);
			System.out.println("rarity class: " + rarityClass);

			TooltipInfoWrapper tooltipInfo = new TooltipInfoWrapper(name, info, iconPath, rarityClass);
			tooltipInfoCache.put(name, tooltipInfo);
			return tooltipInfo;
		}
	}

	/**
	 * Complete.
	 */
	public BankWrapper getReducksData() {
		return getBankData(reducksFile);
	}

	/**
	 * Complete. Retrieves bank content information (comma separated values) as well as bank gold and last updated time
	 * from file.
	 */
	private BankWrapper getBankData(String filename) {
		List<BankItemWrapper> bankItemWrappers = new ArrayList<BankItemWrapper>();
		long lastUpdated = 0;
		int gold = 0;

		try {
			File file = new File(filename);
			Scanner scanner = new Scanner(new BufferedReader(new FileReader(filename)));

			lastUpdated = file.lastModified();
			gold = Integer.parseInt(scanner.next());

			while(scanner.hasNext()) {
				String type = "type not initialized";
				String title = "title not initialized";
				int quantity = 0;
				int ambiguousID = 0;

				String next = scanner.nextLine();

				/**
				 * All lines should be in the format "type, quantity, name" except the first line, empty lines, and comments
				 */
				String[] split = next.split("[,]");

				if(split.length == 3) {
					type = RuruUtility.removeStartingSpaces(split[0]);
					quantity = Integer.parseInt(RuruUtility.removeStartingSpaces(split[1]));
					title = RuruUtility.removeStartingSpaces(split[2]);

					bankItemWrappers.add(new BankItemWrapper(type, title, quantity));

				} else if(split.length == 4) {
					type = RuruUtility.removeStartingSpaces(split[0]);
					quantity = Integer.parseInt(RuruUtility.removeStartingSpaces(split[1]));
					title = RuruUtility.removeStartingSpaces(split[2]);
					ambiguousID = Integer.parseInt(RuruUtility.removeStartingSpaces(split[3]));

					bankItemWrappers.add(new BankItemWrapper(type, title, quantity, ambiguousID));
				} else {
					// skip line and do nothing
				}
			}
			scanner.close();
		} catch(Exception ex) {
			ex.printStackTrace();
		}

		return new BankWrapper(bankItemWrappers, lastUpdated, gold);
	}

	/**
	 * Complete. Reads the dungeon file and assembles a hashmap of abbreviations and full names.
	 * 
	 * @return
	 */
	private HashMap<String, Dungeon> getDungeonData() {
		String filename = dungeonFile;
		HashMap<String, Dungeon> map = new HashMap<String, Dungeon>();

		try {
			Scanner scanner = new Scanner(new BufferedReader(new FileReader(filename)));
			while(scanner.hasNext()) {
				String next = scanner.nextLine();
				StringTokenizer st = new StringTokenizer(next);

				if(st.hasMoreTokens()) {
					int separator = next.indexOf(':');
					String key = next.substring(0, separator);
					String fullname = next.substring(separator + 2, next.length());

					map.put(key, new Dungeon(fullname));
				} else {
					// hmm I dunno if not having anything here is bad or not >.>
				}
			}
			scanner.close();
		} catch(Exception ex) {
			ex.printStackTrace();
		}

		System.out.println("getDungeonData done: " + (System.currentTimeMillis() - start) + "ms");
		return map;
	}

	/**
	 * Complete. Retrieves boss data file in the following format:
	 * <ul>
	 * <li>jeklik: High Priestess Jeklik</li>
	 * <li>venoxis: High Priest Venoxis</li>
	 * <li>marli: High Priestess Mar'li</li>
	 * <li>mandokir: Bloodlord Mandokir</li>
	 * </ul>
	 * 
	 * Keys and values are separated by ": ". Blank lines don't matter. Returns a list of BossWrappers.
	 */
	private HashMap<String, Boss> getBossData() {
		String filename = bossFile;
		HashMap<String, Boss> map = new HashMap<String, Boss>();

		try {
			Scanner scanner = new Scanner(new BufferedReader(new FileReader(filename)));
			while(scanner.hasNext()) {
				String next = scanner.nextLine();
				StringTokenizer st = new StringTokenizer(next);

				if(st.hasMoreTokens()) {
					int separator = next.indexOf(':');
					String key = next.substring(0, separator);
					String fullname = next.substring(separator + 2, next.length());

					map.put(key, new Boss(fullname));
				} else {
					// blank line
				}
			}
			scanner.close();
		} catch(Exception ex) {
			ex.printStackTrace();
		}

		System.out.println("getBossData done: " + (System.currentTimeMillis() - start) + "ms");
		return map;
	}

	/**
	 * Complete. Gets a data file in the following format:
	 * <ul>
	 * <li>priest: deadlyprayer fidela estaog jirien aeruru</li>
	 * <li>mage: kyruru niafic faera malodor</li>
	 * <li>warlock: luigijr mb hagard razgriz</li>
	 * <li>rogue: bagles zeruina dctherogue</li>
	 * </ul>
	 * 
	 * Each line is prefixed with "classname: " and then raider key names delimited by " " Does not handle blank lines.
	 * Returns a list of RaiderWrappers.
	 */
	private HashMap<String, Raider> getRaiderData() {
		HashMap<String, Raider> map = new HashMap<String, Raider>();
		List<String> retired = new ArrayList<String>(); // strings of retired raiders' names

		try {
			Scanner scanner = new Scanner(new BufferedReader(new FileReader(retiredFile)));

			while(scanner.hasNext()) {
				String line = scanner.nextLine();

				StringTokenizer tokenizer = new StringTokenizer(line);
				while(tokenizer.hasMoreTokens()) {
					String raiderName = tokenizer.nextToken();
					retired.add(raiderName);
				}
			}
			scanner.close();
		} catch(Exception ex) {
			ex.printStackTrace();
		}

		try {
			Scanner scanner = new Scanner(new BufferedReader(new FileReader(raiderFile)));

			while(scanner.hasNext()) {
				String line = scanner.nextLine();
				int position = line.indexOf(':');
				String wowClass = line.substring(0, position);

				StringTokenizer tokenizer = new StringTokenizer(line.substring(position + 1, line.length()));
				while(tokenizer.hasMoreTokens()) {
					String raiderName = tokenizer.nextToken();
					Raider raider = new Raider(raiderName, wowClass);
					if(retired.contains(raiderName)) {
						raider.setRetired();
					}

					map.put(raiderName, raider);
				}
			}
			scanner.close();
		} catch(Exception ex) {
			ex.printStackTrace();
		}

		System.out.println("getRaiderData done: " + (System.currentTimeMillis() - start) + "ms");
		return map;
	}

	/**
	 * Returns a list of RaidWrapper objects. The raid data files are ":" file format followed by comma separated values
	 * for loot
	 */
	private List<Raid> getRaidData() {
		// if(raids != null) {
		// return raids;
		// } else {
		// raids = new ArrayList<Raid>();

		List<Raid> local = new ArrayList<Raid>();

		try {
			File dir = new File(raidDir);

			String[] children = dir.list();
			if(children == null) {
				System.out.println("uh oh, " + dir + " is empty");
			} else {
				for(int i = 0; i < children.length; i++) {
					// Get filename of file or directory and read information from the file
					String filename = children[i];
					if(filename.equals(".svn") || filename.equals("notRecent")) {
						// do nothing
					} else {
						local.add(readRaid(dir + "/" + filename));
					}
				}
			}

			/* the notRecent folder is to minimize package explorer tree size, and hold older raids */
			dir = new File(raidDir + "/notRecent");
			children = dir.list();

			if(children == null) {
				System.out.println("uh oh, " + dir + " is empty");
			} else {
				for(int i = 0; i < children.length; i++) {
					// Get filename of file or directory and read information from the file
					String filename = children[i];
					if(!filename.equals(".svn")) {
						local.add(readRaid(dir + "/" + filename));
					}
				}
			}
		} catch(Exception ex) {
			ex.printStackTrace();
		}

		Collections.sort(local);

		System.out.println("getRaidData done: " + (System.currentTimeMillis() - start) + "ms");
		return local;
	}

	private Raid readRaid(String filename) {
		SimpleDateFormat sdf = new SimpleDateFormat("hh:mma EEE MM-dd-yyyy");
		StringTokenizer st;

		List<Raider> participantsList = new ArrayList<Raider>();
		List<RaidPointException> exceptionsList = new ArrayList<RaidPointException>();
		List<LootAssignment> lootAssignmentList = new ArrayList<LootAssignment>();

		Date startTime = null;
		Date endTime = null;
		String dungeonNameKey = null;
		String dungeonName = null;
		String description = null;

		double pointGain = 0.0;
		int position = 0;

		try {
			Scanner scanner = new Scanner(new BufferedReader(new FileReader(filename)));

			/** first line: dungeon */
			String dungeon = scanner.nextLine();
			position = dungeon.indexOf(':');
			if(!dungeon.substring(0, position).equals("dungeon")) {
				throw new IllegalStateException("dungeon name is not specified in the correct format");
			} else {
				dungeonNameKey = dungeon.substring(position + 2, dungeon.length());
				if(!dungeons.containsKey(dungeonNameKey)) {
					throw new IllegalArgumentException("dungeonName " + dungeonNameKey
							+ " has not been initialized in dungeons.txt");
				}
				dungeonName = dungeons.get(dungeonNameKey).getName();
			}

			/** line: description */
			String descLine = scanner.nextLine();
			position = descLine.indexOf(':');
			if(!descLine.substring(0, position).equals("description")) {
				throw new IllegalStateException("raid description is not specified in the correct format");
			} else {
				description = descLine.substring(position + 2, descLine.length());
			}

			/** line: start date */
			String start = scanner.nextLine();
			position = start.indexOf(':');
			if(!start.substring(0, position).equals("start")) {
				throw new IllegalStateException("start date is not specified in the correct format");
			} else {
				// startTime = new Date(Long.parseLong(start.substring(position + 2, start.length())));
				startTime = sdf.parse((start.substring(position + 2, start.length())));
			}

			/** line: end date */
			String end = scanner.nextLine();
			position = end.indexOf(':');
			if(!end.substring(0, position).equals("end")) {
				throw new IllegalStateException("end date is not specified in the correct format");
			} else {
				// endTime = new Date(Long.parseLong(end.substring(position + 2, end.length())));
				endTime = sdf.parse(end.substring(position + 2, end.length()));
			}

			/** line: points */
			String pointsLine = scanner.nextLine();
			position = pointsLine.indexOf(':');
			if(!pointsLine.substring(0, position).equals("points")) {
				throw new IllegalStateException("point gain is not specified in the correct format");
			} else {
				pointGain = Double.parseDouble(pointsLine.substring(position + 2, pointsLine.length()));
			}

			/** line: raiders */
			String raidersLine = scanner.nextLine();
			position = raidersLine.indexOf(':');
			if(!raidersLine.substring(0, position).equals("raiders")) {
				throw new IllegalStateException("raiders are not specified in the correct format");
			} else {
				String raidParticipants = raidersLine.substring(position + 2, raidersLine.length());
				st = new StringTokenizer(raidParticipants);

				while(st.hasMoreTokens()) {
					String raider = st.nextToken();
					if(!raiders.containsKey(raider)) {
						throw new IllegalArgumentException("Raider " + raider + " is not part of raiders.txt");
					}
					participantsList.add(raiders.get(raider));
				}
			}

			/** line: exceptions */
			String exceptions = scanner.nextLine();
			position = exceptions.indexOf(':');
			if(!exceptions.substring(0, position).equals("exceptions")) {
				throw new IllegalStateException("exceptions are not specified in the correct format");
			} else if(exceptions.length() > "exceptions: ".length()) {
				String exceptionsBody = exceptions.substring(position + 2, exceptions.length());
				st = new StringTokenizer(exceptionsBody);

				while(st.hasMoreTokens()) {
					StringTokenizer inner = new StringTokenizer(st.nextToken(","));
					while(inner.hasMoreTokens()) {
						String raiderName = inner.nextToken();
						String modifier = inner.nextToken();
						exceptionsList.add(new RaidPointException(raiders.get(raiderName), Double.valueOf(modifier)
								.doubleValue()));
					}
				}
			}

			/** line: blank */
			if(!scanner.nextLine().equals("")) {
				throw new IllegalStateException("should be a blank line after exceptions are declared");
			}

			/** rest of the lines: loot assignments */
			while(scanner.hasNext()) {
				String assignment = scanner.nextLine();
				String raiderName, itemName, bossName, bidType;

				String[] split = assignment.split("[,]");
				if(split.length != 4) {
					throw new IllegalStateException("loot assignments have incorrect format: --" + assignment
							+ "-- in file " + filename);
				}

				raiderName = RuruUtility.removeStartingSpaces(split[0]);
				bossName = RuruUtility.removeStartingSpaces(split[1]);
				itemName = RuruUtility.removeStartingSpaces(split[2]);
				bidType = RuruUtility.removeStartingSpaces(split[3]);

				if(!bosses.containsKey(bossName)) {
					throw new IllegalArgumentException("bossName " + bossName + " not initialized in bosses.txt");
				}

				lootAssignmentList.add(new LootAssignment(raiders.get(raiderName), new BossDrop(bosses.get(bossName),
						itemName), bidType));
				// lootDropsList.add(new LootDropWrapper(raider, item, boss, bidType));
			}
			scanner.close();
		} catch(Exception ex) {
			ex.printStackTrace();
		}

		Raid raid = new Raid(new Dungeon(dungeonName), pointGain, description, startTime, endTime);

		for(Raider part : participantsList) {
			raid.addRaider(part.getName(), raiders);
		}
		for(LootAssignment assign : lootAssignmentList) {
			raid.addLootAssignment(assign.getRaider(), assign.getBossDrop().getName(), assign.getBossDrop().getBoss(),
					assign.getBidType());
		}
		for(RaidPointException ex : exceptionsList) {
			raid.addException(ex);
		}

		return raid;
	}

	/**
	 * Complete. Returns a long representing the Date at which the latest modification were made to raids in the raid
	 * directory.
	 * 
	 * @return
	 */
	public Long getLastModifiedRaidTime() {
		long latestModification = 0l;
		try {
			File dir = new File(raidDir);

			String[] children = dir.list();
			if(children == null) {
				System.out.println("uh oh, " + dir.getAbsolutePath() + " is empty");
			} else {
				for(int i = 0; i < children.length; i++) {
					String filename = children[i];

					// Get latest modification date
					File file = new File(dir + "/" + filename);

					if(file.lastModified() >= latestModification) {
						latestModification = file.lastModified();
					}
				}
			}
		} catch(Exception ex) {
			ex.printStackTrace();
		}

		return new Long(latestModification);
	}

	public StandingsDataWrapper getStandingsData() {
		StandingsDataWrapper sdw = new StandingsDataWrapper();
		for(String name : raiders.keySet()) {
			Raider raider = raiders.get(name);
			sdw.addEntry(raider.getColoredHtmlClassString(), raider.getRaidClass(), RuruUtility.capFirstLetter(raider
					.getName()), raider.getPoints(), raider.getLiquidity(), raider.getLastRaidDate(), raider.isRetired());
		}

		return sdw;
	}

	/**
	 * The following hacks are a workaround to the "not included in the set of types which can be serialized by this
	 * SerializationPolicy" bug. It is GWT issue #1569.
	 */
	public StandingsDataRow standingsDataRowHack() {
		return null;
	}

	public DkpChangeWrapper dkpChangeWrapperHack() {
		return null;
	}
}
